Ответы к упражнениям
=====================

**Программа: Здравствуй Мир (стр ???)**

* Вариант 1

.. code-block:: d

    import std.stdio;

    void main()
    {
        writeln("Something else... :p");
    }

* Вариант 2

.. code-block:: d

    import std.stdio;

    void main()
    {
        writeln("A line...");
        writeln("Another line...");
    }

* Вариант 3

Следующая программа не может быть скомпилирована, потому что точка с запятой в конце
строки writeln отсутствует:

.. code-block:: d

    import std.stdio;

    void main()
    {
        writeln("Hello, World!") // ← compilation ERROR
    }

**write  и writeln**

Один из методов - использовать другой параметр между ними:

.. code-block:: d

    writeln("Hello, World!", " ", "Hello, fish!");

write может принимать несмколько параметров

.. code-block:: d

    write("one", " two", " three");

**Фундаментальные типы**

Можно использовать другие типы не только int

.. code-block:: d

    import std.stdio;

    void main()
    {
        writeln("Type           : ", short.stringof);
        writeln("Length in bytes: ", short.sizeof);
        writeln("Minimum value  : ", short.min);
        writeln("Maximum value  : ", short.max);
        writeln("Initial value  : ", short.init);
        writeln();
        writeln("Type           : ", ulong.stringof);
        writeln("Length in bytes: ", ulong.sizeof);
        writeln("Minimum value  : ", ulong.min);
        writeln("Maximum value  : ", ulong.max);
        writeln("Initial value  : ", ulong.init);
    }

.. _Присваивание и порядок вычисления выражений:

**Присваивание и порядок вычисления выражений**

Значения a, b и c выводятся в правой части каждой операции. Значение
значение, которое изменяется при каждой операции, выделено:

.. code-block:: console

    at the beginning   →     a 1, b 2, c irrelevant
    c = a              →     a 1, b 2, c 1
    a = b              → a 2, b 2, c 1
    b = c              →     a 2, b 1, c 1

В конце значения a и b поменялись местами.

.. _ответы_к_главе_6:

Переменные

.. code-block:: d

    import std.stdio;

    void main()
    {
        int amount = 20;
        double rate = 2.11;
        writeln("I have exchanged ", amount,
            " Euros at the rate of ", rate);
    }

    
.. _ответы_к_главе_7:

**Стандартный поток ввода и стандартный поток вывода**

.. code-block:: d

    import std.stdio;

    void main()
    {
        stdout.write(1, ",", 2);
        // To complete the line if needed:
        writeln();
    }

**Чтение со стандартного потока ввода**

Когда символы не могут быть преобразованы в нужный тип, stdin переходит в
непригодное для использования состояние. Например, ввод "abc", когда ожидается ввод int, сделает
stdin непригодным для использования.


**Логические выражения**

#. Поскольку компилятор распознает значение 10 < уже как выражение, он
ожидает запятую после него, чтобы принять его как законный аргумент для writeln. Использование
круглых скобок вокруг всего выражения тоже не сработает, потому что в этот раз
потому что в этом случае после выражения будет ожидаться закрывающая скобка.
#. Группировка выражения в виде (10 < value) < 20 устраняет ошибку компиляции
ошибку компиляции, потому что в этом случае сначала оценивается 10 < value, а затем его результат
используется вместе с < 20.
Мы знаем, что значение логического выражения типа 10 < value - это либо false, либо true.
false или true. false и true участвуют в целочисленных выражениях как 0 и 1,
соответственно. (Автоматические преобразования типов мы рассмотрим в одной из следующих глав.) В результате
В результате все выражение эквивалентно либо 0 < 20, либо 1 < 20, оба
из которых оцениваются как true.
#. Выражение "больше нижнего значения и меньше верхнего значения"
можно закодировать следующим образом:

.. code-block:: d

    writeln("Находится между: ", (value > 10) && (value < 20));

#. "Для каждого найдется велосипед" можно закодировать так: personCount <=
bicycleCount или bicycleCount >= personCount. Остальная часть логического
логического выражения можно непосредственно перевести в D из упражнения:

.. code-block:: d

    writeln("Мы идем на пляж: ",
    ((distance < 10) && (bicycleCount >= personCount))
    ||
    ((personCount <= 5) && existsCar && existsLicense)
    );

Обратите внимание на размещение оператора ||, чтобы помочь читаемости, разделяя
два основных условия.

**Предложение if**

#. Оператор writeln("Мыть тарелку") написан с отступом, как будто для того, чтобы
чтобы оказаться в области видимости else. Однако, поскольку область видимости else не
не заключена в фигурные скобки, только оператор writeln("Съесть пирог") находится
на самом деле находится в области видимости else.
Поскольку пробельные символы в программах на языке D не важны, оператор plate
фактически является независимым оператором внутри main() и выполняется
безусловно. Кроме того, оно сбивает читателя с толку тем, что
отступов больше, чем обычно. Если утверждение plate действительно должно находиться в области else
то вокруг него должны быть фигурные скобки:

.. code-block:: d

    import std.stdio;

    void main()
    {
        bool existsLemonade = true;
        if (existsLemonade)
        {
            writeln("Drinking lemonade");
            writeln("Washing the cup");
        }
        else
        {
            writeln("Eating pie");
            writeln("Washing the plate");
        }
    }

#. Мы можем придумать не один дизайн для условий этой игры. I
покажу два примера. В первом мы используем информацию, полученную непосредственно
из упражнения:

.. code-block:: d

    import std.stdio;

    void main()
    {
        write("What is the value of the die? ");
        int die;
        readf(" %s", &die);
        if (die == 1)
        {
            writeln("You won");
        }
        else if (die == 2)
        {
            writeln("You won");
        }
        else if (die == 3)
        {
            writeln("You won");
        }
        else if (die == 4)
        {
            writeln("I won");
        }
        else if (die == 5)
        {
            writeln("I won");
        }
        else if (die == 6)
        {
            writeln("I won");
        }
        else
        {
            writeln("ERROR: ", die, " is invalid");
        }
    }


К сожалению, эта программа имеет много повторений. Мы можем добиться того же
результата другими способами. Вот один из них:

.. code-block:: d

    import std.stdio;

    void main()
    {
        write("What is the value of the die? ");
        int die;
        readf(" %s", &die);
        if ((die == 1) || (die == 2) || (die == 3))
        {
            writeln("You won");
        }
        else if ((die == 4) || (die == 5) || (die == 6))
        {
            writeln("I won");
        }
        else
        {
            writeln("ERROR: ", die, " is invalid");
        }
    }

#. В этом случае нельзя использовать предыдущие конструкции. Непрактично вводить
1000 различных значений в программе и ожидать, что все они будут правильными или читаемыми.
По этой причине лучше определить, находится ли значение кубика в пределах
диапазоне:

.. code-block:: d

    if ((die >= 1) && (die <= 500))

**Цикл while**

#. Поскольку начальное значение числа равно 0, логическое выражение цикла while
ложно с самого начала, и это мешает войти в
тело цикла. Решением является использование начального значения, которое позволит условию while
быть истинным в самом начале:

.. code-block:: d

    int number = 3;

#. Все переменные в следующей программе по умолчанию инициализированы в 0. Это позволяет
позволяет войти в оба цикла хотя бы один раз:

.. code-block:: d

    import std.stdio;

    void main()
    {
        int secretNumber;
        while ((secretNumber < 1) || (secretNumber > 10))
        {
            write("Please enter a number between 1 and 10: ");
            readf(" %s", &secretNumber);
        }
        int guess;
        while (guess != secretNumber)
        {
            write("Guess the secret number: ");
            readf(" %s", &guess);
        }
        writeln("That is correct!");
    }

.. _Арифметические операции с целыми числами:

**Арифметические операции с целыми числами**

#. Мы можем использовать оператор / для деления и оператор % для
остатка:

.. code-block:: d

    import std.stdio;

    void main()
    {
        int first;
        write("Please enter the first number: ");
        readf(" %s", &first);
        int second;
        write("Please enter the second number: ");
        readf(" %s", &second);
        int quotient = first / second;
        int remainder = first % second;
        writeln(first, " = ",
            second, " * ", quotient, " + ", remainder);
    }

#. Мы можем определить, равен ли остаток 0 или нет, с помощью оператора if:

.. code-block:: d

    import std.stdio;

    void main()
    {
        int first;
        write("Please enter the first number: ");
        readf(" %s", &first);
        int second;
        write("Please enter the second number: ");
        readf(" %s", &second);
        int quotient = first / second;
        int remainder = first % second;
        // We cannot call writeln up front before determining
        // whether the remainder is 0 or not. We must terminate
        // the line later with a writeln.
        write(first, " = ", second, " * ", quotient);
        // The remainder must be printed only if nonzero.
        if (remainder != 0)
        {
            write(" + ", remainder);
        }
        // We are now ready to terminate the line.
        writeln();
    }


.. code-block:: d

    import std.stdio;

    void main()
    {
        while (true)
        {
            write("0: Exit, 1: Add, 2: Subtract, 3: Multiply,",
                " 4: Divide - Please enter the operation: ");
            int operation;
            readf(" %s", &operation);
            // Let's first validate the operation
            if ((operation < 0) || (operation > 4))
            {
                writeln("I don't know this operation");
                continue;
            }
            if (operation == 0)
            {
                writeln("Goodbye!");
                break;
            }
            // If we are here, we know that we are dealing with
            // one of the four operations. Now is the time to read
            // two integers from the user:
            int first;
            int second;
            write(" First number: ");
            readf(" %s", &first);
            write("Second number: ");
            readf(" %s", &second);
            int result;
            if (operation == 1)
            {
                result = first + second;
            }
            else if (operation == 2)
            {
                result = first - second;
            }
            else if (operation == 3)
            {
                result = first * second;
            }
            else if (operation == 4)
            {
                result = first / second;
            }
            else
            {
                writeln(
                    "There is an error! ",
                    "This condition should have never occurred.");
                break;
            }
            writeln("       Result: ", result);
        }
    }

.. code-block:: d

    import std.stdio;

    void main()
    {
        int value = 1;
        while (value <= 10)
        {
            if (value != 7)
            {
                writeln(value);
            }
            ++value;
        }
    }

**Типы с плавающей точкой**

#. Замена float на double приводит к результату, который удивляет по-другому.


.. code-block:: d

    // ...double result = 0;
    // ...
    if (result == 1)
    {
        writeln("As expected: 1");
    }
    else
    {
        Exercise Solutions 700 writeln("DIFFERENT: ", result);
    }

Хотя значение не проходит сравнение result == 1, оно все равно выводится как 1:

.. code-block:: console

    DIFFERENT: 1

Этот удивительный результат связан с тем, как значения с плавающей точкой
форматируются для печати. Более точное приближение к значению можно увидеть
когда значение печатается с большим количеством цифр после десятичной точки. (Мы
рассмотрим форматированный вывод в одной из следующих глав (стр. ???)):

.. code-block:: d

    writefln("DIFFERENT: %.20f", result);

.. code-block:: console

    DIFFERENT: 1.00000000000000066613

#. Достаточно заменить целый тип на double:

.. code-block:: d

    double first;
    double second;
    // ...
    double result;

#. Следующая программа демонстрирует, насколько сложнее было бы
если бы потребовалось более пяти переменных:

.. code-block:: d

    import std.stdio;

    void main()
    {
        double value_1;
        double value_2;
        double value_3;
        double value_4;
        double value_5;
        write("Value 1: ");
        readf(" %s", &value_1);
        write("Value 2: ");
        readf(" %s", &value_2);
        write("Value 3: ");
        readf(" %s", &value_3);
        write("Value 4: ");
        readf(" %s", &value_4);
        write("Value 5: ");
        readf(" %s", &value_5);
        writeln("Twice the values:");
        writeln(value_1 * 2);
        writeln(value_2 * 2);
        writeln(value_3 * 2);
        writeln(value_4 * 2);
        writeln(value_5 * 2);
        writeln("One fifth the values:");
        writeln(value_1 / 5);
        writeln(value_2 / 5);
        writeln(value_3 / 5);
        writeln(value_4 / 5);
        writeln(value_5 / 5);
    }

.. _решения для главы массивы:

**Массивы**

.. code-block:: d

    import std.stdio;

    void main()
    {
        double value_1;
        double value_2;
        double value_3;
        double value_4;
        double value_5;
        write("Value 1: ");
        readf(" %s", &value_1);
        write("Value 2: ");
        readf(" %s", &value_2);
        write("Value 3: ");
        readf(" %s", &value_3);
        write("Value 4: ");
        readf(" %s", &value_4);
        write("Value 5: ");
        readf(" %s", &value_5);
        writeln("Twice the values:");
        writeln(value_1 * 2);
        writeln(value_2 * 2);
        writeln(value_3 * 2);
        writeln(value_4 * 2);
        writeln(value_5 * 2);
        writeln("One fifth the values:");
        writeln(value_1 / 5);
        writeln(value_2 / 5);
        writeln(value_3 / 5);
        writeln(value_4 / 5);
        writeln(value_5 / 5);
    }

#. Пояснения включены в код в виде комментариев:

.. code-block:: d

    import std.stdio;
    import std.algorithm;

    void main()
    {
        // Using dynamic arrays because it is not known how many
        // values are going to be read from the input
        int[] odds;
        int[] evens;
        writeln("Please enter integers (-1 to terminate):");
        while (true)
        {
            // Reading the value
            int value;
            readf(" %s", &value);
            // The special value of -1 breaks the loop
            if (value == -1)
            {
                break;
            }
            // Adding to the corresponding array, depending on
            // whether the value is odd or even. It is an even
            // number if there is no remainder when divided by 2.
            if ((value % 2) == 0)
            {
                evens ~= value;
            }
            else
            {
                odds ~= value;
            }
        }
        // The odds and evens arrays are sorted separately
        sort(odds);
        sort(evens);
        // The two arrays are then appended to form a new array
        int[] result;
        result = odds ~ evens;
        writeln("First the odds then the evens, sorted:");
        // Printing the array elements in a loop
        int i;
        while (i < result.length)
        {
            write(result[i], " ");
            ++i;
        }
        writeln();
    }

#. В этой программе есть три ошибки. Первые две связаны с
циклами while: В обоих условиях цикла используется оператор <= вместо оператора <
оператора. В результате программа использует неверные индексы и пытается получить доступ к
элементам, которые не являются частями массивов.
Поскольку вам будет полезнее самостоятельно отладить третью ошибку, я
хочу, чтобы вы сначала запустили программу после исправления двух предыдущих ошибок. Вы
заметите, что программа не выводит результаты. Сможете ли вы выяснить
оставшуюся проблему до прочтения следующего абзаца?
Значение i равно 5 в момент завершения первого цикла while, и это значение
приводит к тому, что логическое выражение второго цикла становится ложным, что в свою очередь
препятствует входу во второй цикл. Решение состоит в том, чтобы сбросить i в 0 перед
второго цикла while, например, с помощью оператора i = 0;

**Фрагменты и прочие функции массивов**

Итерация по элементам путем использования фрагмента с самого начала - это интересная
концепция. Этот метод также лежит в основе диапазонов Phobos, которые мы рассмотрим в следующей главе
главе.

.. code-block:: d

    import std.stdio;

    void main()
    {
        double[] array = [1, 20, 2, 30, 7, 11];
        double[] slice = array; // Start with a slice that
        // provides access to all of
        // the elements of the array
        while (slice.length)
        { // As long as there is at least
            // one element in that slice
            if (slice[0] > 10)
            { // Always use the first element
                slice[0] /= 2; // in the expressions
            }
            slice = slice[1 .. $]; // Shorten the slice from the
            // beginning
        }
        writeln(array); // The actual elements are
        // changed
    }

**Строки**

#. Хотя некоторые функции в модулях Phobos легко использовать с
строк, документация по библиотеке обычно более краткая, чем учебники. Вы
особенно диапазоны Phobos, могут показаться вам запутанными. Мы рассмотрим
Диапазоны Phobos в одной из следующих глав.

#. В цепочку можно объединить и многие другие функции:

.. code-block:: d

    import std.stdio;
    import std.string;

    void main()
    {
        write("First name: ");
        string first = capitalize(strip(readln()));
        write("Last name: ");
        string last = capitalize(strip(readln()));
        string fullName = first ~ " " ~ last;
        writeln(fullName);
    }

#. Эта программа использует два индекса для создания среза:

.. code-block:: d

    import std.stdio;
    import std.string;

    void main()
    {
        write("Please enter a line: ");
        string line = strip(readln());
        ptrdiff_t first_e = indexOf(line, 'e');
        if (first_e == -1)
        {
            writeln("There is no letter e in this line.");
        }
        else
        {
            ptrdiff_t last_e = lastIndexOf(line, 'e');
            writeln(line[first_e .. last_e + 1]);
        }
    }

**Перенаправления потоков стандартного ввода/вывода**

Перенаправление стандартного ввода и вывода программ широко используется
особенно в оболочках операционных систем на базе Unix. (Оболочка - это программа, которая
взаимодействующая с пользователем в терминале.) Некоторые программы разработаны таким образом, чтобы хорошо работать
при передаче другим программам.
Например, файл с именем deneme.d можно искать в дереве каталогов с помощью
соединив программы find и grep, как показано в следующей строке:


.. code-block:: console

    find | grep deneme.d

find выводит имена всех файлов на свой вывод. grep получает этот вывод
и выводит строки, содержащие deneme.d, в свой собственный вывод.

.. _решения для главы файлы:

**Файлы**

.. code-block:: d

    import std.stdio;
    import std.string;

    void main()
    {
        write("Please enter the name of the file to read from: ");
        string inFileName = strip(readln());
        File inFile = File(inFileName, "r");
        string outFileName = inFileName ~ ".out";
        File outFile = File(outFileName, "w");
        while (!inFile.eof())
        {
            string line = strip(inFile.readln());
            if (line.length != 0)
            {
                outFile.writeln(line);
            }
        }
        writeln(outFileName, " has been created.");
    }

.. _auto and typeof:

**auto и typeof**

Мы можем использовать typeof для определения типа литерала и .stringof для получения
имя этого типа в виде строки:

.. code-block:: d

    import std.stdio;

    void main()
    {
        writeln(typeof(1.2).stringof);
    }

Вывод:

.. code-block:: console

    double

**Цикл for**

.. code-block:: d

    import std.stdio;

    void main()
    {
        for (int line = 0; line != 9; ++line)
        {
            for (int column = 0; column != 9; ++column)
            {
                write(line, ',', column, ' ');
            }
            writeln();
        }
    }

треугольник

.. code-block:: d

    import std.stdio;

    void main()
    {
        for (int line = 0; line != 5; ++line)
        {
            int length = line + 1;
            for (int i = 0; i != length; ++i)
            {
                write('*');
            }
            writeln();
        }
    }

Параллелограмм:

.. code-block:: d

    import std.stdio;

    void main()
    {
        for (int line = 0; line != 5; ++line)
        {
            for (int i = 0; i != line; ++i)
            {
                write(' ');
            }
            writeln("********");
        }
    }

Можете ли вы изготовить алмазный узор?

.. code-block:: console

     *
    ***
    *****
    *******
    *****
    ***
     *

.. _Тернарный оператор:

**Тернарный оператор**

Хотя в этом упражнении, возможно, разумнее использовать оператор if-else,
в следующей программе используются два оператора ?:

.. code-block:: d

    import std.stdio;

    void main()
    {
        write("Please enter the net amount: ");
        int amount;
        readf(" %s", &amount);
        writeln("$",
            amount < 0 ? -amount : amount,
            amount < 0 ? " lost" : " gained");
    }

Программа печатает

.. code-block:: console

    "gained",

даже если значение равно нулю. Измените программу, чтобы
выводить сообщение, более подходящее для нуля.

**Литералы**

#. Проблема здесь в том, что значение в правой части слишком велико, чтобы поместиться в
int. Согласно правилам о целочисленных литералах, его тип - long. По этой
поэтому оно не соответствует типу переменной в левой части. Существует как минимум
как минимум два решения.
Одно решение - оставить тип переменной на усмотрение компилятора, например
с помощью ключевого слова auto:

.. code-block:: d

    auto amount = 10_000_000_000;


Тип переменной amount будет выведен как long из ее начального значения из
правой части.
Другое решение - сделать тип переменной тоже long:

.. code-block:: d

    long amount = 10_000_000_000;

#. Мы можем воспользоваться специальным символом '\r', который переносит печать в
начало строки.

.. code-block:: d

    import std.stdio;

    void main()
    {
        for (int number = 0;; ++number)
        {
            write("\rNumber: ", number);
        }
    }

#. Вывод этой программы может быть нестабильным из-за ее взаимодействия с
буфером вывода. Следующая программа промывает буфер вывода и ожидает
10 миллисекунд после каждой записи:

.. code-block:: d

    import std.stdio;
    import core.thread;

    void main()
    {
        for (int number = 0;; ++number)
        {
            write("\rNumber: ", number);
            stdout.flush();
            Thread.sleep(10.msecs);
        }
    }

Сброс вывода обычно не требуется, так как он сбрасывается автоматически
перед переходом к следующей строке, например, с помощью writeln, или перед чтением из stdin.

.. _Форматированный вывод:

**Форматированный вывод**


#. Мы уже видели, что это тривиально с помощью спецификаторов формата:

.. code-block:: d

    import std.stdio;

    void main()
    {
        writeln("(Enter 0 to exit the program.)");
        while (true)
        {
            write("Please enter a number: ");
            long number;
            readf(" %s", &number);
            if (number == 0)
            {
                break;
            }
            writefln("%1$d <=> %1$#x", number);
        }
    }

#. Помня, что символ % должен дважды появиться в строке формата, чтобы
чтобы он был напечатан:

.. code-block:: d

    import std.stdio;

    void main()
    {
        write("Please enter the percentage value: ");
        double percentage;
        readf(" %s", &percentage);
        writefln("%%%.2f", percentage);
    }

**Форматированный ввод**

Использование строки формата, в которой части даты заменяются на %s, было бы достаточно
достаточно:

.. code-block:: d

    import std.stdio;

    void main()
    {
        int year;
        int month;
        int day;
        readf("%s.%s.%s", &year, &month, &day);
        writeln("Month: ", month);
    }

**Цикл do-while**

Эта программа не имеет прямого отношения к циклу do-while, поскольку любая проблема, которая решается
решается с помощью цикла do-while, может быть решена и с помощью других операторов цикла.
Программа может угадать число, о котором думает пользователь, сокращая
диапазон кандидатов сверху или снизу в соответствии с ответами пользователя. Для
Например, если первое угаданное число равно 50, а пользователь отвечает, что секретное число
больше, программа поймет, что число должно находиться в диапазоне
[51,100]. Если затем программа угадает другое число прямо в середине этого диапазона
то на этот раз будет известно, что число находится либо в диапазоне [51,75], либо в
диапазоне [76,100].
Когда размер диапазона равен 1, программа будет уверена, что это должно быть то
число, которое угадал пользователь.

**Aссоциативные массивы**

#. Свойство .keys возвращает фрагмент (т. е. динамический массив), включающий все
ключи ассоциативного массива. Итерация по этому фрагменту и удаление
элемент для каждого ключа, вызвав .remove, приведет к пустому
ассоциативному массиву:

.. code-block:: d

    import std.stdio;

    void main()
    {
        string[int] names =
            [
                1: "one",
                10: "ten",
                100: "hundred",
            ];
        writeln("Initial length: ", names.length);
        int[] keys = names.keys;
        /* 'foreach' is similar but superior to 'for'. We will
    * see the 'foreach' loop in the next chapter. */
        foreach (key; keys)
        {
            writefln("Removing the element %s", key);
            names.remove(key);
        }
        writeln("Final length: ", names.length);
    }

Такое решение может быть медленным, особенно для больших массивов. Следующие
методы опустошат массив за один шаг.

* Другое решение - присвоить пустой массив:

.. code-block:: d

    string[int] emptyAA;
    names = emptyAA;

* Поскольку начальным значением массива в любом случае является пустой массив, следующая
  техника позволит добиться того же результата:

.. code-block:: d

    names = names.init;

#. Цель состоит в том, чтобы хранить несколько оценок для каждого ученика. Поскольку несколько оценок можно
   храниться в динамическом массиве, здесь подойдет ассоциативный массив, который отображает строки в
   int[]. Оценки могут быть добавлены к динамическим массивам
   которые хранятся в ассоциативном массиве:

.. code-block:: d

    import std.stdio;

    void main()
    {
        /* The key type of this associative array is string and
    * the value type is int[], i.e. an array of ints. The
    * associative array is being defined with an extra
    * space in between to help distinguish the value type: */
        int[][string] grades;
        /* The array of ints that correspond to "emre" is being
    * used for appending the new grade to that array: */
        grades["emre"] ~= 90;
        grades["emre"] ~= 85;
        /* Printing the grades of "emre": */
        writeln(grades["emre"]);
    }

Оценки также могут быть присвоены за один раз с помощью литерала массива:


.. code-block:: d

    import std.stdio;

    void main()
    {
        int[][string] grades;
        grades["emre"] = [90, 85, 95];
        writeln(grades["emre"]);
    }

**Цикл foreach**

Чтобы получить ассоциативный массив, работающий наоборот с именами, необходимо поменять местами типы ключа
и значения. Новый ассоциативный массив должен быть определен как типа int[string].
Итерация по ключам и значениям исходного ассоциативного массива при
используя ключи как значения, а значения как ключи, заполнит таблицу значений:

.. code-block:: d

    import std.stdio;

    void main()
    {
        string[int] names = [1: "one", 7: "seven", 20: "twenty"];
        int[string] values;
        foreach (key, value; names)
        {
            values[value] = key;
        }
        writeln(values["twenty"]);
    }

**switch и case**

.. code-block:: d

    import std.stdio;
    import std.string;

    void main()
    {
        string op;
        double first;
        double second;
        write("Please enter the operation: ");
        op = strip(readln());
        write("Please enter two values separated by a space: ");
        readf(" %s %s", &first, &second);
        double result;
        final switch (op)
        {
        case "add":
            result = first + second;
            break;
        case "subtract":
            result = first - second;
            break;
        case "multiply":
            result = first * second;
            break;
        case "divide":
            result = first / second;
            break;
        }
        writeln(result);
    }

#. Используя преимущества отдельных значений случая:

.. code-block:: d

    final switch (op)
    {
    case "add", "+" : result = first + second;
        break;
    case "subtract", "-" : result = first - second;
        break;
    case "multiply", "*" : result = first * second;
        break;
    case "divide", "/" : result = first / second;
        break;
    }

#. Поскольку секция по умолчанию нужна для выброса исключения, она не может быть
заключительным оператором switch. Вот части программы, которые
изменены:

.. code-block:: d

    // ...
    switch (op)
    {
        // ...
    default : throw new Exception("Invalid operation: " ~ op);
    }
    // ...

**enum**

.. code-block:: d

    import std.stdio;
    import std.conv;

    enum Operation
    {
        exit,
        add,
        subtract,
        multiply,
        divide
    }

    void main()
    {
        // Print the supported operations
        write("Operations - ");
        for (Operation operation; operation <= Operation.max; ++operation)
        {
            writef("%d:%s ", operation, operation);
        }
        writeln();
        // Unconditional loop until the user wants to exit
        while (true)
        {
            write("Operation? ");
            // The input must be read in the actual type (int) of
            // the enum
            int operationCode;
            readf(" %s", &operationCode);
            /* We will start using enum values instead of magic
    * constants from this point on. So, the operation code
    * that has been read in int must be converted to its
    * corresponding enum value.
    *
    * (Type conversions will be covered in more detail in
    * a later chapter.) */
            Operation operation = cast(Operation) operationCode;
            if ((operation < Operation.min) ||
                (operation > Operation.max))
            {
                writeln("ERROR: Invalid operation");
                continue;
            }
            if (operation == Operation.exit)
            {
                writeln("Goodbye!");
                break;
            }
            double first;
            double second;
            double result;
            write(" First operand? ");
            readf(" %s", &first);
            write("Second operand? ");
            readf(" %s", &second);
            switch (operation)
            {
            case Operation.add:
                result = first + second;
                break;
            case Operation.subtract:
                result = first - second;
                break;
            case Operation.multiply:
                result = first * second;
                break;
            case Operation.divide:
                result = first / second;
                break;
            default:
                throw new Exception(
                    "ERROR: This line should have never been reached.");
            }
            writeln("        Result: ", result);
        }
    }

**Функции**

#. .. code-block:: d

    import std.stdio;

    void printMenu(string[] items, int firstNumber)
    {
        foreach (i, item; items)
        {
            writeln(' ', i + firstNumber, ' ', item);
        }
    }

    void main()
    {
        string[] items =
            ["Black", "Red", "Green", "Blue", "White"];
        printMenu(items, 1);
    }

#. Вот несколько идей:
* Напишите функцию drawHorizontalLine() для рисования горизонтальных линий.
* Напишите функцию drawSquare() для рисования квадратов. Эта функция
может использовать преимущества функций drawVerticalLine() и
drawHorizontalLine() при рисовании квадрата.
* Усовершенствуйте функции, чтобы они также принимали символ, который используется при
"рисования". Это позволит рисовать каждую фигуру с разным
символом:

.. code-block:: d

    void putDot(Canvas canvas, int line, int column, dchar dot)
    {
        canvas[line][column] = dot;
    }

.. _Параметры функций решения:

**Параметры функций**

Поскольку параметры этой функции копируются из
аргументов, то в функции меняются местами именно эти копии.
Чтобы функция поменяла местами аргументы, оба параметра должны быть
переданы по ссылке:

.. code-block:: d

    void swap(ref int first, ref int second)
    {
        const int temp = first;
        first = second;
        second = temp;
    }

При таком изменении переменные в main() поменяются местами:

.. code-block:: d

    2 1

Хотя это и не относится к исходной проблеме, обратите внимание, что temp указана как
const, поскольку в функции она не изменяется.

**Програмное окружение**

#. .. code-block:: d

    import std.stdio;
    import std.conv;

    int main(string[] args)
    {
        if (args.length != 4)
        {
            stderr.writeln(
                "ERROR! Usage: \n    ", args[0],
                " a_number operator another_number");
            return 1;
        }
        immutable first = to!double(args[1]);
        string op = args[2];
        immutable second = to!double(args[3]);
        switch (op)
        {
        case "+":
            writeln(first + second);
            break;
        case "-":
            writeln(first - second);
            break;
        case "x":
            writeln(first * second);
            break;
        case "/":
            writeln(first / second);
            break;
        default:
            throw new Exception("Invalid operator: " ~ op);
        }
        return 0;
    }

#. .. code-block:: d

    import std.stdio;
    import std.process;

    void main()
    {
        write("Please enter the command line to execute: ");
        string commandLine = readln();
        writeln("The output: ", executeShell(commandLine));
    }

.. _assert и enforce:

**assert и enforce**

#. Вы заметите, что программа завершается нормально, когда вы вводите 06:09
и 1:2. Однако вы можете заметить, что время начала не совпадает с тем, которое было
введенное пользователем:

.. code-block:: console

    1 hours and 2 minutes after 09:06 is 10:08.

Как видите, несмотря на то, что время было введено как 06:09, вывод
содержит 09:06. Эта ошибка будет выявлена с помощью утверждения в следующей
задаче.

#. Сбой утверждения после ввода 06:09 и 15:2 приводит нас к следующей строке
строке:

.. code-block:: d

    string timeToString(int hour, int minute)
    {
        assert((hour >= 0) && (hour <= 23));
        // ...
    }

Для того чтобы проверка не прошла, эта функция должна быть вызвана с недопустимым
значением часа.
Единственные два вызова функции timeToString() в программе, похоже, не имеют
никаких проблем:

.. code-block:: d

    writefln("%s hours and %s minutes after %s is %s.",
        durationHour, durationMinute,
        timeToString(startHour, startMinute),
        timeToString(endHour, endMinute));

Дополнительное расследование должно выявить истинную причину ошибки: Час
и минута меняются местами при чтении начального времени:

.. code-block:: d

    readTime("Start time", startMinute, startHour);

Эта ошибка программирования приводит к тому, что время интерпретируется как 09:06 и
Увеличение его на длительность 15:2 приводит к недопустимому значению часа.
Очевидное исправление состоит в том, чтобы передавать переменные часа и минуты в правильном
порядке:

.. code-block:: d

    readTime("Start time", startHour, startMinute);

Вывод:

.. code-block:: console

    Start time? (HH:MM) 06:09
    Duration? (HH:MM) 15:2
    15 hours and 2 minutes after 06:09 is 21:11.

#. Это снова та же самая проверка утверждений:

.. code-block:: d

    assert((hour >= 0) && (hour <= 23));

Причина в том, что функция addDuration() может выдавать значения часов, которые больше
чем 23. Добавление операции остатка в конце обеспечит одну из
гарантий вывода функции:

.. code-block:: d

    void addDuration(int startHour, int startMinute,
        int durationHour, int durationMinute,
        out int resultHour, out int resultMinute)
    {
        resultHour = startHour + durationHour;
        resultMinute = startMinute + durationMinute;
        if (resultMinute > 59)
        {
            ++resultHour;
        }
        resultHour %= 24;
    }

Обратите внимание, что у функции есть и другие проблемы. Например, результатМинута
может оказаться больше 59. Следующая функция вычисляет
значение минуты правильно и гарантирует, что гарантии вывода функции
соблюдаются:

.. code-block:: d

    void addDuration(int startHour, int startMinute,
        int durationHour, int durationMinute,
        out int resultHour, out int resultMinute)
    {
        resultHour = startHour + durationHour;
        resultMinute = startMinute + durationMinute;
        resultHour += resultMinute / 60;
        resultHour %= 24;
        resultMinute %= 60;
        assert((resultHour >= 0) && (resultHour <= 23));
        assert((resultMinute >= 0) && (resultMinute <= 59));
    }

#. Удачи

**Модульное тестирование**

Первое, что нужно сделать, - это скомпилировать и запустить программу, чтобы убедиться, что тесты
действительно работают и не приводят к сбоям:

.. code-block:: console

    $ dmd deneme.d -w -unittest
    $ ./deneme
    core.exception.AssertError@deneme(11): unittest failure

Строка с номером 11 указывает на то, что первый из тестов завершился неудачей.
Для демонстрации напишем явно некорректную реализацию
которая случайно проходит первый тест. Следующая функция просто возвращает копию
входных данных:

.. code-block:: d

    dstring toFront(dstring str, dchar letter)
    {
        dstring result;
        foreach (c; str)
        {
            result ~= c;
        }
        return result;
    }

    unittest
    {
        immutable str = "hello"d;
        assert(toFront(str, 'h') == "hello");
        assert(toFront(str, 'o') == "ohell");
        assert(toFront(str, 'l') == "llheo");
    }

    void main()
    {
    }

Первый тест проходит успешно, но второе падает

.. code-block:: console

    $ ./deneme
    core.exception.AssertError@deneme.d(17): unittest failure

Вот правильная реализация, которая проходит все тесты:

.. code-block:: d

    dstring toFront(dstring str, dchar letter)
    {
        dchar[] firstPart;
        dchar[] lastPart;
        foreach (c; str)
        {
            if (c == letter)
            {
                firstPart ~= c;
            }
            else
            {
                lastPart ~= c;
            }
        }
        return (firstPart ~ lastPart).idup;
    }

    unittest
    {
        immutable str = "hello"d;
        assert(toFront(str, 'h') == "hello");
        assert(toFront(str, 'o') == "ohell");
        assert(toFront(str, 'l') == "llheo");
    }

    void main()
    {
    }

Тесты проходят

.. code-block:: d

    $ ./deneme
    $

Теперь эту функцию можно модифицировать различными способами, проверяя модификации тестами.
Следующие две реализации сильно отличаются от первой, но они тоже корректны согласно тестам.

* Данная реализация использует возможности модуля std.algorithm.partition:

.. code-block:: d

    import std.algorithm;

    dstring toFront(dstring str, dchar letter)
    {
        dchar[] result = str.dup;
        partition!(c => c == letter, SwapStrategy.stable)(result);
        return result.idup;
    }

    unittest
    {
        immutable str = "hello"d;
        assert(toFront(str, 'h') == "hello");
        assert(toFront(str, 'o') == "ohell");
        assert(toFront(str, 'l') == "llheo");
    }

    void main()
    {
    }

*Примечание: синтаксис=>, который встречается в приведенной выше программе, создает лямбда-функцию.
Мы будем рассматривать лямбда-функции в последующих главах.*

* В следующей реализации сначала подсчитывается, сколько раз специальная буква
  встречается в строке. Затем эта информация передается в отдельную функцию
  repeated() для получения первой части результата. Обратите внимание, что
  repeated() имеет свой собственный набор модульных тестов:

.. code-block:: d

    dstring repeated(size_t count, dchar letter)
    {
        dstring result;
        foreach (i; 0 .. count)
        {
            result ~= letter;
        }
        return result;
    }

    unittest
    {
        assert(repeated(3, 'z') == "zzz");
        assert(repeated(10, 'é') == "éééééééééé");
    }

    dstring toFront(dstring str, dchar letter)
    {
        size_t specialLetterCount;
        dstring lastPart;
        foreach (c; str)
        {
            if (c == letter)
            {
                ++specialLetterCount;
            }
            else
            {
                lastPart ~= c;
            }
        }
        return repeated(specialLetterCount, letter) ~ lastPart;
    }

    unittest
    {
        immutable str = "hello"d;
        assert(toFront(str, 'h') == "hello");
        assert(toFront(str, 'o') == "ohell");
        assert(toFront(str, 'l') == "llheo");
    }

    void main()
    {
    }

.. _решения_Контрактное_программирование:

**Контрактное программирование**

Блок unittest может быть реализован тривиально, путем копирования проверок, которые
которые уже написаны в main(). Единственным дополнением ниже является тест для случая, когда
вторая команда выигрывает:

.. code-block:: d

    int addPoints(int goals1, int goals2,
        ref int points1, ref int points2)
    in
    {
        assert(goals1 >= 0);
        assert(goals2 >= 0);
        assert(points1 >= 0);
        assert(points2 >= 0);
    }
    out (result)
    {
        assert((result >= 0) && (result <= 2));
    }
    do
    {
        int winner;
        if (goals1 > goals2)
        {
            points1 += 3;
            winner = 1;
        }
        else if (goals1 < goals2)
        {
            points2 += 3;
            winner = 2;
        }
        else
        {
            ++points1;
            ++points2;
            winner = 0;
        }
        return winner;
    }

    unittest
    {
        int points1 = 10;
        int points2 = 7;
        int winner;
        // First team wins
        winner = addPoints(3, 1, points1, points2);
        assert(points1 == 13);
        assert(points2 == 7);
        assert(winner == 1);
        Exercise Solutions 717// Draw
        winner = addPoints(2, 2, points1, points2);
        assert(points1 == 14);
        assert(points2 == 8);
        assert(winner == 0);
        // Second team wins
        winner = addPoints(0, 1, points1, points2);
        assert(points1 == 14);
        assert(points2 == 11);
        assert(winner == 2);
    }

    void main()
    {
        // ...
    }


Для этой функции могут быть полезны контракты на основе выражений:

.. code-block:: d

    int addPoints(int goals1, int goals2,
        ref int points1, ref int points2)
    in (goals1 >= 0)
    in (goals2 >= 0)
    in (points1 >= 0)
    in (points2 >= 0)
    out (result; (result >= 0) && (result <= 2))
    {
        // ...
    }

**Структуры**

#. Одна из самых простых конструкций - использование двух членов dchar:

.. code-block:: d

    struct Card
    {
        dchar suit;
        dchar value;
    }

#. Достаточно просто напечатать два члена рядом друг с другом:

.. code-block:: d

    void printCard(Card card)
    {
        write(card.suit, card.value);
    }

#. Если предположить, что уже существует функция newSuit(), то newDeck() можно реализовать, вызывая эту функцию для каждой масти.
   можно реализовать, вызывая эту функцию для каждой масти:

.. code-block:: d

    Card[] newDeck()
    out (result)
    {
        assert(result.length == 52);
    }
    do
    {
        Card[] deck;
        deck ~= newSuit('♠');
        deck ~= newSuit('♡');
        deck ~= newSuit('♢');
        deck ~= newSuit('♣');
        return deck;
    }

Остальная часть работы может быть выполнена с помощью следующей функции newSuit(), которая
создает масть, комбинируя символ масти с каждым значением строки:

TODO Card symbol ?????

.. code-block:: d

    Card[] newSuit(dchar suit)
    in
    {
        assert((suit == '♠') ||
                (suit == '♡') ||
                (suit == '♢') ||
                (suit == '♣'));
    }
    out (result)
    {
        assert(result.length == 13);
    }
    do
    {
        Card[] suitCards;
        foreach (value; "234567890JQKA")
        {
            suitCards ~= Card(suit, value);
        }
        return suitCards;
    }


Обратите внимание, что приведенные выше функции используют преимущества контрактного программирования для
снижения риска программных ошибок.

#. Если поменять два элемента местами, то колода будет становиться все более
более перетасованной при каждом повторении. Хотя можно выбрать один и тот же
элемент случайно, замена элемента самим собой не дает никакого эффекта
кроме упущенной возможности получить более перетасованную колоду.

.. code-block:: d

    void shuffle(Card[] deck, int repetition)
    {
        /* Note: A better algorithm is to walk the deck from the
    *       beginning to the end and to swap each element
    *       with a random one that is picked among the
    *       elements from that point to the end.
    *
    * It would be even better to call randomShuffle() from
    * the std.algorithm module, which already applies the
    * same algorithm. Please read the comment in main() to
    * see how randomShuffle() can be used. */
        foreach (i; 0 .. repetition)
        {
            // Pick two elements at random
            immutable first = uniform(0, deck.length);
            immutable second = uniform(0, deck.length);
            swap(deck[first], deck[second]);
        }
    }

Приведенная выше функция вызывает std.algorithm.swap, которая просто меняет местами
значениями двух своих параметров ref. По сути, это эквивалентно
следующей функции:

.. code-block:: d

    void mySwap(ref Card left,
        ref Card right)
    {
        immutable temporary = left;
        left = right;
        right = temporary;
    }


Полная программа:

.. code-block:: d

    import std.stdio;
    import std.random;
    import std.algorithm;

    struct Card
    {
        dchar suit;
        dchar value;
    }

    void printCard(Card card)
    {
        write(card.suit, card.value);
    }

    Card[] newSuit(dchar suit)
    in
    {
        assert((suit == '♠') ||
                (suit == '♡') ||
                (suit == '♢') ||
                (suit == '♣'));
    }
    out (result)
    {
        assert(result.length == 13);
    }
    do
    {
        Card[] suitCards;
        foreach (value; "234567890JQKA")
        {
            suitCards ~= Card(suit, value);
        }
        return suitCards;
    }

    Card[] newDeck()
    out (result)
    {
        assert(result.length == 52);
    }
    do
    {
        Card[] deck;
        deck ~= newSuit('♠');
        deck ~= newSuit('♡');
        deck ~= newSuit('♢');
        deck ~= newSuit('♣');
        return deck;
    }

    void shuffle(Card[] deck, int repetition)
    {
        /* Note: A better algorithm is to walk the deck from the
    *       beginning to the end and to swap each element
    *       with a random one that is picked among the
    *       elements from that point to the end.
    *
    * It would be even better to call randomShuffle() from
    * the std.algorithm module, which already applies the
    * same algorithm. Please read the comment in main() to
    * see how randomShuffle() can be used. */
        foreach (i; 0 .. repetition)
        {
            // Pick two elements at random
            immutable first = uniform(0, deck.length);
            immutable second = uniform(0, deck.length);
            swap(deck[first], deck[second]);
        }
    }

    void main()
    {
        Card[] deck = newDeck();
        shuffle(deck, 100);
        /* Note: Instead of the shuffle() call above, it would be
    *       better to call randomShuffle() as in the
    *       following line:
    *
    * randomShuffle(deck);
    */
        foreach (card; deck)
        {
            printCard(card);
            write(' ');
        }
        writeln();
    }

.. _Переменное число параметров:

**Переменное число параметров**

Чтобы функция calculate() могла принимать переменное количество параметров,
ее список параметров должен включать фрагмент Calculation, за которым следует ...:

.. code-block:: d

    double[] calculate(Calculation[] calculations...)
    {
        double[] results;
        foreach (calculation; calculations)
        {
            final switch (calculation.op)
            {
            case Operation.add:
                results ~= calculation.first + calculation.second;
                break;
            case Operation.subtract:
                results ~= calculation.first - calculation.second;
                break;
            case Operation.multiply:
                results ~= calculation.first * calculation.second;
                break;
            case Operation.divide:
                results ~= calculation.first / calculation.second;
                break;
            }
        }
        return results;
    }

Каждое вычисление выполняется в цикле, а их результаты добавляются в
фрагмент типа double[].
Вот вся программа:

.. code-block:: d

    import std.stdio;

    enum Operation
    {
        add,
        subtract,
        multiply,
        divide
    }

    struct Calculation
    {
        Operation op;
        double first;
        double second;
    }

    double[] calculate(Calculation[] calculations...)
    {
        double[] results;
        foreach (calculation; calculations)
        {
            final switch (calculation.op)
            {
            case Operation.add:
                results ~= calculation.first + calculation.second;
                break;
            case Operation.subtract:
                results ~= calculation.first - calculation.second;
                break;
            case Operation.multiply:
                results ~= calculation.first * calculation.second;
                break;
            case Operation.divide:
                results ~= calculation.first / calculation.second;
                break;
            }
        }
        return results;
    }

    void main()
    {
        writeln(calculate(Calculation(Operation.add, 1.1, 2.2),
                Calculation(Operation.subtract, 3.3, 4.4),
                Calculation(Operation.multiply, 5.5, 6.6),
                Calculation(Operation.divide, 7.7, 8.8)));
    }

Вывод:

.. code-block:: console

    [3.3, -1.1, 36.3, 0.875]

.. _решения Перегрузка функций:

**Перегрузка функций**

Следующие две перегрузки используют преимущества существующих перегрузок info():

.. code-block:: d

    void info(Meal meal)
    {
        info(meal.time);
        write('-');
        info(addDuration(meal.time, TimeOfDay(1, 30)));
        write(" Meal, Address: ", meal.address);
    }

    void info(DailyPlan plan)
    {
        info(plan.amMeeting);
        writeln();
        info(plan.lunch);
        writeln();
        info(plan.pmMeeting);
    }

Ниже полная программа:


.. code-block:: d

    import std.stdio;

    struct TimeOfDay
    {
        int hour;
        int minute;
    }

    void info(TimeOfDay time)
    {
        writef("%02s:%02s", time.hour, time.minute);
    }

    TimeOfDay addDuration(TimeOfDay start,
        TimeOfDay duration)
    {
        TimeOfDay result;
        result.minute = start.minute + duration.minute;
        result.hour = start.hour + duration.hour;
        result.hour += result.minute / 60;
        result.minute %= 60;
        result.hour %= 24;
        return result;
    }

    struct Meeting
    {
        string topic;
        size_t attendanceCount;
        TimeOfDay start;
        TimeOfDay end;
    }

    void info(Meeting meeting)
    {
        info(meeting.start);
        write('-');
        info(meeting.end);
        writef(" \"%s\" meeting with %s attendees",
            meeting.topic,
            meeting.attendanceCount);
    }

    struct Meal
    {
        TimeOfDay time;
        string address;
    }

    void info(Meal meal)
    {
        info(meal.time);
        write('-');
        info(addDuration(meal.time, TimeOfDay(1, 30)));
        write(" Meal, Address: ", meal.address);
    }

    struct DailyPlan
    {
        Meeting amMeeting;
        Meal lunch;
        Meeting pmMeeting;
    }

    void info(DailyPlan plan)
    {
        info(plan.amMeeting);
        writeln();
        info(plan.lunch);
        writeln();
        info(plan.pmMeeting);
    }

    void main()
    {
        immutable bikeRideMeeting = Meeting("Bike Ride", 4,
            TimeOfDay(10, 30),
            TimeOfDay(11, 45));
        immutable lunch = Meal(TimeOfDay(12, 30), "İstanbul");
        immutable budgetMeeting = Meeting("Budget", 8,
            TimeOfDay(15, 30),
            TimeOfDay(17, 30));
        immutable todaysPlan = DailyPlan(bikeRideMeeting,
            lunch,
            budgetMeeting);
        info(todaysPlan);
        writeln();
    }

Функция main() также может быть написана с использованием только объектных литералов:

.. code-block:: d

    void main()
    {
        info(DailyPlan(Meeting("Bike Ride", 4,
                TimeOfDay(10, 30),
                TimeOfDay(11, 45)),
                Meal(TimeOfDay(12, 30), "İstanbul"),
                Meeting("Budget", 8,
                TimeOfDay(15, 30),
                TimeOfDay(17, 30))));
        writeln();
    }


**Функции члены**

#. Потенциально отрицательные промежуточные значения делают функцию decrement() немного сложнее
сложнее, чем инкремент():

.. code-block:: d

    struct TimeOfDay
    {
        // ...
        void decrement(Duration duration)
        {
            auto minutesToSubtract = duration.minute % 60;
            auto hoursToSubtract = duration.minute / 60;
            minute -= minutesToSubtract;
            if (minute < 0)
            {
                minute += 60;
                ++hoursToSubtract;
            }
            hour -= hoursToSubtract;
            if (hour < 0)
            {
                hour = 24 - (-hour % 24);
            }
        }
        // ...
    }



#. Чтобы увидеть, насколько проще становится с функциями-членами toString(), давайте рассмотрим
на перегрузку Meeting функции info() еще раз:

.. code-block:: d

    void info(Meeting meeting)
    {
        info(meeting.start);
        write('-');
        info(meeting.end);
        writef(" \"%s\" meeting with %s attendees",
            meeting.topic,
            meeting.attendanceCount);
    }

Используя преимущества уже определенного TimeOfDay.toString, реализация
реализация Meeting.toString становится тривиальной:

.. code-block:: d

    string toString()
    {
        return format("%s-%s \"%s\" meeting with %s attendees",
            start, end, topic, attendanceCount);
    }

Полная программа:

.. code-block:: d

    import std.stdio;
    import std.string;

    struct Duration
    {
        int minute;
    }

    struct TimeOfDay
    {
        int hour;
        int minute;
        string toString()
        {
            return format("%02s:%02s", hour, minute);
        }

        void increment(Duration duration)
        {
            minute += duration.minute;
            hour += minute / 60;
            minute %= 60;
            hour %= 24;
        }
    }

    struct Meeting
    {
        string topic;
        int attendanceCount;
        TimeOfDay start;
        TimeOfDay end;
        string toString()
        {
            return format("%s-%s \"%s\" meeting with %s attendees",
                start, end, topic, attendanceCount);
        }
    }

    struct Meal
    {
        TimeOfDay time;
        string address;
        string toString()
        {
            TimeOfDay end = time;
            end.increment(Duration(90));
            return format("%s-%s Meal, Address: %s",
                time, end, address);
        }
    }

    struct DailyPlan
    {
        Meeting amMeeting;
        Meal lunch;
        Meeting pmMeeting;
        string toString()
        {
            return format("%s\n%s\n%s",
                amMeeting,
                lunch,
                pmMeeting);
        }
    }

    void main()
    {
        auto bikeRideMeeting = Meeting("Bike Ride", 4,
            TimeOfDay(10, 30),
            TimeOfDay(11, 45));
        auto lunch = Meal(TimeOfDay(12, 30), "İstanbul");
        auto budgetMeeting = Meeting("Budget", 8,
            TimeOfDay(15, 30),
            TimeOfDay(17, 30));
        auto todaysPlan = DailyPlan(bikeRideMeeting,
            lunch,
            budgetMeeting);
        writeln(todaysPlan);
        writeln();
    }

Вывод программы такой же, как и в предыдущей программе, использующей
перегрузки функции info():

.. code-block:: console

    10:30-11:45 "Bike Ride" meeting with 4 attendees
    12:30-14:00 Meal, Address: İstanbul
    15:30-17:30 "Budget" meeting with 8 attendees

.. _Решения Перегрузка операторов:

**Перегрузка операторов**

Следующая реализация проходит все модульные тесты. Проектные решения
были включены в код в виде комментариев.
Некоторые функции этой структуры могут быть реализованы более эффективно.
Кроме того, было бы полезно нормализовать числитель и
знаменателя. Например, вместо того чтобы хранить значения 20 и 60, значения
можно разделить на их наибольший общий делитель, а числитель и
знаменатель можно хранить как 1 и 3. В противном случае большинство операций
над объектом приведут к увеличению значений числителя и знаменателя.
увеличиваться.

.. code-block:: d

    import std.exception;
    import std.conv;

    struct Fraction
    {
        long num; // numerator
        long den; // denominator
        /* As a convenience, the constructor uses the default
    * value of 1 for the denominator. */
        this(long num, long den = 1)
        {
            enforce(den != 0, "The denominator cannot be zero");
            this.num = num;
            this.den = den;
            /* Ensuring that the denominator is always positive
    * will simplify the definitions of some of the
    * operator functions. */
            if (this.den < 0)
            {
                this.num = -this.num;
                this.den = -this.den;
            }
        }
        /* Unary -: Returns the negative of this fraction. */
        Fraction opUnary(string op)() const
        if (op == "-")
        {
            /* Simply constructs and returns an anonymous
    * object. */
            return Fraction(-num, den);
        }
        /* ++: Increments the value of the fraction by one. */
        ref Fraction opUnary(string op)() if (op == "++")
        {
            /* We could have used 'this += Fraction(1)' here. */
            num += den;
            return this;
        }
        /* --: Decrements the value of the fraction by one. */
        ref Fraction opUnary(string op)() if (op == "--")
        {
            /* We could have used 'this -= Fraction(1)' here. */
            num -= den;
            return this;
        }
        /* +=: Adds the right-hand fraction to this one. */
        ref Fraction opOpAssign(string op)(Fraction rhs) if (op == "+")
        {
            /* Addition formula: a/b + c/d = (a*d + c*b)/(b*d) */
            num = (num * rhs.den) + (rhs.num * den);
            den *= rhs.den;
            return this;
        }
        /* -=: Subtracts the right-hand fraction from this one. */
        ref Fraction opOpAssign(string op)(Fraction rhs)if (op == "-")
        {
            /* We make use of the already-defined operators += and
    * unary - here. Alternatively, the subtraction
    * formula could explicitly be applied similar to the
    * += operator above.
    *
    * Subtraction formula: a/b - c/d = (a*d - c*b)/(b*d)
    */
            this +=  - rhs;
            return this;
        }
        /* *=: Multiplies the fraction by the right-hand side. */
        ref Fraction opOpAssign(string op)(Fraction rhs) if (op == "*")
        {
            /* Multiplication formula: a/b * c/d = (a*c)/(b*d) */
            num *= rhs.num;
            den *= rhs.den;
            return this;
        }
        /* /=: Divides the fraction by the right-hand side. */
        ref Fraction opOpAssign(string op)(Fraction rhs) if (op == "/")
        {
            enforce(rhs.num != 0, "Cannot divide by zero");
            /* Division formula: (a/b) / (c/d) = (a*d)/(b*c) */
            num *= rhs.den;
            den *= rhs.num;
            return this;
        }
        /* Binary +: Produces the result of adding this and the
    * right-hand side fractions. */
        Fraction opBinary(string op)(Fraction rhs) const
        if (op == "+")
        {
            /* Takes a copy of this fraction and adds the
    * right-hand side fraction to that copy. */
            Fraction result = this;
            result += rhs;
            return result;
        }
        /* Binary -: Produces the result of subtracting the
    * right-hand side fraction from this one. */
        Fraction opBinary(string op)(Fraction rhs) const
        if (op == "-")
        {
            /* Uses the already-defined -= operator. */
            Fraction result = this;
            result -= rhs;
            return result;
        }
        /* Binary *: Produces the result of multiplying this
    * fraction with the right-hand side fraction. */
        Fraction opBinary(string op)(Fraction rhs) const
        if (op == "*")
        {
            /* Uses the already-defined *= operator. */
            Fraction result = this;
            result *= rhs;
            return result;
        }
        /* Binary /: Produces the result of dividing this fraction
    * by the right-hand side fraction. */
        Fraction opBinary(string op)(Fraction rhs) const
        if (op == "/")
        {
            /* Uses the already-defined /= operator. */
            Fraction result = this;
            result /= rhs;
            return result;
        }

        /* Returns the value of the fraction as double. */
        double opCast(T : double)() const
        {
            /* A simple division. However, as dividing values of
    * type long would lose the part of the value after
    * the decimal point, we could not have written
    * 'num/den' here. */
            return to!double(num) / den;
        }
        /* Sort order operator: Returns a negative value if this
    * fraction is before, a positive value if this fraction
    * is after, and zero if both fractions have the same sort
    * order. */
        int opCmp(const Fraction rhs) const
        {
            immutable result = this - rhs;
            /* Being a long, num cannot be converted to int
    * automatically; it must be converted explicitly by
    * 'to' (or cast). */
            return to!int(result.num);
        }
        /* Equality comparison: Returns true if the fractions are
    * equal.
    *
    * The equality comparison had to be defined for this type
    * because the compiler-generated one would be comparing
    * the members one-by-one, without regard to the actual
    * values that the objects represent.
    *
    * For example, although the values of both Fraction(1,2)
    * and Fraction(2,4) are 0.5, the compiler-generated
    * opEquals would decide that they were not equal on
    * account of having members of different values. */
        bool opEquals(const Fraction rhs) const
        {
            /* Checking whether the return value of opCmp is zero
    * is sufficient here. */
            return opCmp(rhs) == 0;
        }
    }
    unittest
    {
        /* Must throw when denominator is zero. */
        assertThrown(Fraction(42, 0));
        /* Let's start with 1/3. */
        auto a = Fraction(1, 3);
        /* -1/3 */
        assert(-a == Fraction(-1, 3));
        /* 1/3 + 1 == 4/3 */
        ++a;
        assert(a == Fraction(4, 3));
        /* 4/3 - 1 == 1/3 */
        --a;
        assert(a == Fraction(1, 3));
        /* 1/3 + 2/3 == 3/3 */
        a += Fraction(2, 3);
        assert(a == Fraction(1));
        /* 3/3 - 2/3 == 1/3 */
        a -= Fraction(2, 3);
        assert(a == Fraction(1, 3));
        /* 1/3 * 8 == 8/3 */
        a *= Fraction(8);
        assert(a == Fraction(8, 3));
        /* 8/3 / 16/9 == 3/2 */
        a /= Fraction(16, 9);
        assert(a == Fraction(3, 2));
        /* Must produce the equivalent value in type 'double'.
    *
    * Note that although double cannot represent every value
    * precisely, 1.5 is an exception. That is why this test
    * is being applied at this point. */
        assert(to!double(a) == 1.5);
        /* 1.5 + 2.5 == 4 */
        assert(a + Fraction(5, 2) == Fraction(4, 1));
        /* 1.5 - 0.75 == 0.75 */
        assert(a - Fraction(3, 4) == Fraction(3, 4));
        /* 1.5 * 10 == 15 */
        assert(a * Fraction(10) == Fraction(15, 1));
        /* 1.5 / 4 == 3/8 */
        assert(a / Fraction(4) == Fraction(3, 8));
        /* Must throw when dividing by zero. */
        assertThrown(Fraction(42, 1) / Fraction(0));
        /* The one with lower numerator is before. */
        assert(Fraction(3, 5) < Fraction(4, 5));
        /* The one with larger denominator is before. */
        assert(Fraction(3, 9) < Fraction(3, 8));
        assert(Fraction(1, 1_000) > Fraction(1, 10_000));
        /* The one with lower value is before. */
        assert(Fraction(10, 100) < Fraction(1, 2));
        /* The one with negative value is before. */
        assert(Fraction(-1, 2) < Fraction(0));
        assert(Fraction(1, -2) < Fraction(0));
        /* The ones with equal values must be both <= and >=.  */
        assert(Fraction(-1, -2) <= Fraction(1, 2));
        assert(Fraction(1, 2) <= Fraction(-1, -2));
        assert(Fraction(3, 7) <= Fraction(9, 21));
        assert(Fraction(3, 7) >= Fraction(9, 21));
        /* The ones with equal values must be equal. */
        assert(Fraction(1, 3) == Fraction(20, 60));
        /* The ones with equal values with sign must be equal. */
        assert(Fraction(-1, 2) == Fraction(1, -2));
        assert(Fraction(1, 2) == Fraction(-1, -2));
    }

    void main()
    {
    }

Как уже говорилось в этой главе, строковые миксины можно использовать для объединения
определения некоторых операторов. Например, следующее определение охватывает
четыре арифметических оператора:

.. code-block:: d

    /* Binary arithmetic operators. */
    Fraction opBinary(string op)(Fraction rhs) const
    if ((op == "+") || (op == "-") ||
        (op == "*") || (op == "/"))
    {
        /* Takes a copy of this fraction and applies the
    * right-hand side fraction to that copy. */
        Fraction result = this;
        mixin("result " ~ op ~ "= rhs;");
        return result;
    }

**Наследование**

#. Функции-члены, объявленные суперклассами как абстрактные, должны быть
должны быть определены с помощью ключевого слова override в подклассах.
Игнорируя класс Train для этого упражнения, Locomotive.makeSound и
RailwayCar.makeSound могут быть реализованы, как показано в следующей программе:

.. code-block:: d

    import std.stdio;
    import std.exception;

    class RailwayVehicle
    {
        void advance(size_t kilometers)
        {
            writefln("The vehicle is advancing %s kilometers",
                kilometers);
            foreach (i; 0 .. kilometers / 100)
            {
                writefln("  %s", makeSound());
            }
        }

        abstract string makeSound();
    }

    class Locomotive : RailwayVehicle
    {
        override string makeSound()
        {
            return "choo choo";
        }
    }

    class RailwayCar : RailwayVehicle
    {
        // ...
        override string makeSound()
        {
            return "clack clack";
        }
    }

    class PassengerCar : RailwayCar
    {
        // ...
    }

    class FreightCar : RailwayCar
    {
        // ...
    }

    void main()
    {
        auto railwayCar1 = new PassengerCar;
        railwayCar1.advance(100);
        auto railwayCar2 = new FreightCar;
        railwayCar2.advance(200);
        auto locomotive = new Locomotive;
        locomotive.advance(300);
    }

#. Следующая программа использует звуки компонентов поезда для создания
звук самого поезда:

.. code-block::

    import std.stdio;
    import std.exception;

    class RailwayVehicle
    {
        void advance(size_t kilometers)
        {
            writefln("The vehicle is advancing %s kilometers",
                kilometers);
            foreach (i; 0 .. kilometers / 100)
            {
                writefln("  %s", makeSound());
            }
        }

        abstract string makeSound();
    }

    class Locomotive : RailwayVehicle
    {
        override string makeSound()
        {
            return "choo choo";
        }
    }

    class RailwayCar : RailwayVehicle
    {
        abstract void load();
        abstract void unload();
        override string makeSound()
        {
            return "clack clack";
        }
    }

    class PassengerCar : RailwayCar
    {
        override void load()
        {
            writeln("The passengers are getting on");
        }

        override void unload()
        {
            writeln("The passengers are getting off");
        }
    }

    class FreightCar : RailwayCar
    {
        override void load()
        {
            writeln("The crates are being loaded");
        }

        override void unload()
        {
            writeln("The crates are being unloaded");
        }
    }

    class Train : RailwayVehicle
    {
        Locomotive locomotive;
        RailwayCar[] cars;
        this(Locomotive locomotive)
        {
            enforce(locomotive !is null,
                "Locomotive cannot be null");
            this.locomotive = locomotive;
        }

        void addCar(RailwayCar[] cars...)
        {
            this.cars ~= cars;
        }

        override string makeSound()
        {
            string result = locomotive.makeSound();
            foreach (car; cars)
            {
                result ~= ", " ~ car.makeSound();
            }
            return result;
        }

        void departStation(string station)
        {
            foreach (car; cars)
            {
                car.load();
            }
            writefln("Departing from %s station", station);
        }

        void arriveStation(string station)
        {
            writefln("Arriving at %s station", station);
            foreach (car; cars)
            {
                car.unload();
            }
        }
    }

    void main()
    {
        auto locomotive = new Locomotive;
        auto train = new Train(locomotive);
        train.addCar(new PassengerCar, new FreightCar);
        train.departStation("Ankara");
        train.advance(500);
        train.arriveStation("Haydarpaşa");
    }


Вывод:

.. code-block:: console

    The passengers are getting on
    The crates are being loaded
    Departing from Ankara station
    The vehicle is advancing 500 kilometers
    choo choo, clack clack, clack clack
    choo choo, clack clack, clack clack
    choo choo, clack clack, clack clack
    choo choo, clack clack, clack clack
    choo choo, clack clack, clack clack
    Arriving at Haydarpaşa station
    The passengers are getting off
    The crates are being unloaded

.. _решения Объекты:

**Объекты**


#. Для сравнения на равенство достаточно, чтобы rhs был не нулевым, а члены были равны:

.. code-block:: d

    enum Color
    {
        blue,
        green,
        red
    }

    class Point
    {
        int x;
        int y;
        Color color;
        // ...
        override bool opEquals(Object o) const
        {
            const rhs = cast(const Point) o;
            return rhs && (x == rhs.x) && (y == rhs.y);
        }
    }

#. Если тип объекта правой стороны также является Point, они сравниваются
сначала по значениям членов x, а затем по
значениям членов y:

.. code-block:: d

    class Point
    {
        int x;
        int y;
        Color color;
        // ...
        override int opCmp(Object o) const
        {
            const rhs = cast(const Point) o;
            enforce(rhs);
            return (x != rhs.x
                    ? x - rhs.x : y - rhs.y);
        }
    }

#. Обратите внимание, что невозможно привести к типу const TriangularArea внутри
opCmp. Если rhs - const TriangularArea, то его член
rhs.points также будет const. Поскольку параметр opCmp является
не является const, то передать rhs.points[i] в point.opCmp будет невозможно.

.. code-block:: d

    class TriangularArea
    {
        Point[3] points;
        this(Point one, Point two, Point three)
        {
            points = [one, two, three];
        }

        override bool opEquals(Object o) const
        {
            const rhs = cast(const TriangularArea) o;
            return rhs && (points == rhs.points);
        }

        override int opCmp(Object o) const
        {
            auto rhs = cast(TriangularArea) o;
            enforce(rhs);
            foreach (i, point; points)
            {
                immutable comparison = point.opCmp(rhs.points[i]);
                if (comparison != 0)
                {
                    /* The sort order has already been
    * determined. Simply return the result. */
                    return comparison;
                }
            }
            /* The objects are considered equal because all of
    * their points have been equal. */
            return 0;
        }

        override size_t toHash() const
        {
            /* Since the 'points' member is an array, we can take
    * advantage of the existing toHash algorithm for
    * array types. */
            return typeid(points).getHash(&points);
        }
    }

**УКазатели**


#. Если параметры являются типами значений, такими как int, аргументы копируются в
функции. Предпочтительным способом определения ссылочных параметров является указание
их как ref.
Другой способ - определить параметры как указатели, которые указывают на фактические
переменные. Части программы, которые были изменены, выделены:

.. code-block:: d

    void swap(int* lhs, int* rhs)
    {
        int temp = *lhs;
        *lhs = *rhs;
        *rhs = temp;
    }

    void main()
    {
        int i = 1;
        int j = 2;
        swap(&i, &j);
        assert(i == 2);
        assert(j == 1);
    }

Проверки в конце программы проходят

#. Node и List были написаны для работы только с типом int. Мы можем
преобразовать эти типы в шаблоны struct, добавив (T) после их имен и
заменив соответствующие int в их определениях на Ts:

.. code-block:: d

    struct Node(T)
    {
        T element;
        Node* next;
        string toString() const
        {
            string result = to!string(element);
            if (next)
            {
                result ~= " -> " ~ to!string(*next);
            }
            return result;
        }
    }

    struct List(T)
    {
        Node!T* head;
        void insertAtHead(T element)
        {
            head = new Node!T(element, head);
        }

        string toString() const
        {
            return format("(%s)", head ? to!string(*head) : "");
        }
    }

Список теперь можно использовать с любым типом:

.. code-block:: d

    import std.stdio;
    import std.conv;
    import std.string;

    // ...
    struct Point
    {
        double x;
        double y;
        string toString() const
        {
            return format("(%s,%s)", x, y);
        }
    }

    void main()
    {
        List!Point points;
        points.insertAtHead(Point(1.1, 2.2));
        points.insertAtHead(Point(3.3, 4.4));
        points.insertAtHead(Point(5.5, 6.6));
        writeln(points);
    }

Вывод:

.. code-block:: console

    ((5.5,6.6) -> (3.3,4.4) -> (1.1,2.2))

#. В этом случае нам нужен еще один указатель, который будет указывать на последний узел списка. Код
новый код обязательно усложняется, чтобы управлять новой переменной
также:

.. code-block:: d

    struct List(T)
    {
        Node!T* head;
        Node!T* tail;
        void append(T element)
        {
            /* Since there is no node after the last one, we set
    * the new node's next pointer to 'null'. */
            auto newNode = new Node!T(element, null);
            if (!head)
            {
                /* The list has been empty. The new node becomes
    * the head. */
                head = newNode;
            }
            if (tail)
            {
                /* We place this node after the current tail. */
                tail.next = newNode;
            }
            /* The new node becomes the new tail. */
            tail = newNode;
        }

        void insertAtHead(T element)
        {
            auto newNode = new Node!T(element, head);
            /* The new node becomes the new head. */
            head = newNode;
            if (!tail)
            {
                /* The list has been empty. The new node becomes
    * the tail. */
                tail = newNode;
            }
        }

        string toString() const
        {
            return format("(%s)", head ? to!string(*head) : "");
        }
    }


Новая реализация insertAtHead() на самом деле может быть короче:

.. code-block:: d

    void insertAtHead(T element)
    {
        head = new Node!T(element, head);
        if (!tail)
        {
            tail = head;
        }
    }

Следующая программа использует новый Список для вставки объектов Point с нечетными значениями в начале и объектов Point с четными значениями в конце.
в начало и объектов Point с четными значениями в конец.

.. code-block:: d

    void main()
    {
        List!Point points;
        foreach (i; 1 .. 7)
        {
            if (i % 2)
            {
                points.insertAtHead(Point(i, i));
            }
            else
            {
                points.append(Point(i, i));
            }
        }
        writeln(points);
    }

Вывод:

.. code-block:: d

    ((5,5) -> (3,3) -> (1,1) -> (2,2) -> (4,4) -> (6,6))

**Битовые операции**

#. Возможно, в такой короткой функции допустимо использовать магические константы.
В противном случае код может стать слишком сложным.

.. code-block:: d

    string dotted(uint address)
    {
        return format("%s.%s.%s.%s",
            (address >> 24) & 0xff,
            (address >> 16) & 0xff,
            (address >> 8) & 0xff,
            (address >> 0) & 0xff);
    }

Поскольку тип является беззнаковым, биты, которые вставляются в значение
с левой стороны, будут иметь значение 0. По этой причине нет необходимости
маскировать значение, сдвинутое на 24 бита. Кроме того, поскольку сдвиг на 0
битов не имеет никакого эффекта, эту операцию также можно исключить:

.. code-block:: d

    string dotted(uint address)
    {
        return format("%s.%s.%s.%s",
            address >> 24,
            (address >> 16) & 0xff,
            (address >> 8) & 0xff,
            address & 0xff);
    }

#. Каждый октет может быть сдвинут в соответствующую позицию в адресе IPv4, а затем
эти выражения могут быть "сгруппированы":

.. code-block:: d

    uint ipAddress(ubyte octet3, // most significant octet
        ubyte octet2,
        ubyte octet1,
        ubyte octet0)
    { // least significant octet
        return (octet3 << 24) |
            (octet2 << 16) |
            (octet1 << 8) |
            (octet0 << 0);
    }

#. Следующий метод начинается со значения, все биты которого равны 1. Сначала
значение сдвигается вправо, чтобы старшие биты стали 0, а затем
сдвигается влево, чтобы младшие биты стали 0:

.. code-block:: d

    uint mask(int lowestBit, int width)
    {
        uint result = uint.max;
        result >>= (uint.sizeof * 8) - width;
        result <<= lowestBit;
        return result;
    }

uint.max - это значение, при котором все биты равны 1. В качестве альтернативы вычисление
можно начать со значения, которое является дополнением 0, что совпадает с
uint.max:

.. code-block:: d

    uint result = ~0;
    // ...

**foreach для структур и классов**

#. Размер шага должен храниться рядом с началом и концом, а значение элемента
должно быть увеличено на этот размер шага:

.. code-block:: d

    struct NumberRange
    {
        int begin;
        int end;
        int stepSize;
        int opApply(int delegate(ref int) dg) const
        {
            int result;
            for (int number = begin; number != end; number += stepSize)
            {
                result = dg(number);
                if (result)
                {
                    break;
                }
            }
            return result;
        }
    }

    import std.stdio;

    void main()
    {
        foreach (element; NumberRange(0, 10, 2))
        {
            write(element, ' ');
        }
    }


#. .. code-block:: d

    import std.stdio;
    import std.string;

    class Student
    {
        string name;
        int id;
        this(string name, int id)
        {
            this.name = name;
            this.id = id;
        }

        override string toString()
        {
            return format("%s(%s)", name, id);
        }
    }

    class Teacher
    {
        string name;
        string subject;
        this(string name, string subject)
        {
            this.name = name;
            this.subject = subject;
        }

        override string toString()
        {
            return format("%s teacher %s", subject, name);
        }
    }

    class School
    {
    private:
        Student[] students;
        Teacher[] teachers;
    public:
        this(Student[] students, Teacher[] teachers)
        {
            this.students = students;
            this.teachers = teachers;
        }
        /* This opApply override will be called when the foreach
    * variable is a Student. */
        int opApply(int delegate(ref Student) dg)
        {
            int result;
            foreach (student; students)
            {
                result = dg(student);
                if (result)
                {
                    break;
                }
            }
            return result;
        }
        /* Similarly, this opApply will be called when the foreach
    * variable is a Teacher. */
        int opApply(int delegate(ref Teacher) dg)
        {
            int result;
            foreach (teacher; teachers)
            {
                result = dg(teacher);
                if (result)
                {
                    break;
                }
            }
            return result;
        }
    }

    void printIndented(T)(T value)
    {
        writeln("  ", value);
    }

    void main()
    {
        auto school = new School(
            [
            new Student("Can", 1),
            new Student("Canan", 10),
            new Student("Cem", 42),
            new Student("Cemile", 100)
        ],
        [new Teacher("Nazmiye", "Math"),
            new Teacher("Makbule", "Literature")]);
        writeln("Student loop");
        foreach (Student student; school)
        {
            printIndented(student);
        }
        writeln("Teacher loop");
        foreach (Teacher teacher; school)
        {
            printIndented(teacher);
        }
    }

Вывод:

.. code-block:: console

    Student loop
    Can(1)
    Canan(10)
    Cem(42)
    Cemile(100)
    Teacher loop
    Math teacher Nazmiye
    Literature teacher Makbule

Как вы можете видеть, реализация обеих переопределений opApply()
абсолютно одинаковы, за исключением того, на каком фрагменте они выполняют итерацию. Чтобы уменьшить количество
дублирования кода, общую функциональность можно перенести в шаблон реализации
шаблон функции, который затем будет вызываться двумя переопределениями opApply():

.. code-block:: d

    class School
    {
        // ...
        int opApplyImpl(T)(T[] slice, int delegate(ref T) dg)
        {
            int result;
            foreach (element; slice)
            {
                result = dg(element);
                if (result)
                {
                    break;
                }
            }
            return result;
        }

        int opApply(int delegate(ref Student) dg)
        {
            return opApplyImpl(students, dg);
        }

        int opApply(int delegate(ref Teacher) dg)
        {
            return opApplyImpl(teachers, dg);
        }
    }
